home *** CD-ROM | disk | FTP | other *** search
/ Game.EXE 2001 January / Game.EXE_01_2001.iso / demos / Blade of Darkness / data1.cab / Program_Executable_Files / Lib / PythonLib / sgmllib.py < prev    next >
Encoding:
Python Source  |  2000-11-16  |  14.5 KB  |  447 lines

  1. # A parser for SGML, using the derived class as static DTD.
  2.  
  3. # XXX This only supports those SGML features used by HTML.
  4.  
  5. # XXX There should be a way to distinguish between PCDATA (parsed
  6. # character data -- the normal case), RCDATA (replaceable character
  7. # data -- only char and entity references and end tags are special)
  8. # and CDATA (character data -- only end tags are special).
  9.  
  10.  
  11. import re
  12. import string
  13.  
  14.  
  15. # Regular expressions used for parsing
  16.  
  17. interesting = re.compile('[&<]')
  18. incomplete = re.compile('&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|'
  19.                            '<([a-zA-Z][^<>]*|'
  20.                               '/([a-zA-Z][^<>]*)?|'
  21.                               '![^<>]*)?')
  22.  
  23. entityref = re.compile('&([a-zA-Z][a-zA-Z0-9]*)[^a-zA-Z0-9]')
  24. charref = re.compile('&#([0-9]+)[^0-9]')
  25.  
  26. starttagopen = re.compile('<[>a-zA-Z]')
  27. shorttagopen = re.compile('<[a-zA-Z][a-zA-Z0-9]*/')
  28. shorttag = re.compile('<([a-zA-Z][a-zA-Z0-9]*)/([^/]*)/')
  29. endtagopen = re.compile('</[<>a-zA-Z]')
  30. endbracket = re.compile('[<>]')
  31. special = re.compile('<![^<>]*>')
  32. commentopen = re.compile('<!--')
  33. commentclose = re.compile('--[ \t\n]*>')
  34. tagfind = re.compile('[a-zA-Z][a-zA-Z0-9]*')
  35. attrfind = re.compile(
  36.     '[ \t\n]+([a-zA-Z_][-.a-zA-Z_0-9]*)'
  37.     '([ \t\n]*=[ \t\n]*'
  38.     r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./:+*%?!\(\)_#=~]*))?')
  39.  
  40.  
  41. # SGML parser base class -- find tags and call handler functions.
  42. # Usage: p = SGMLParser(); p.feed(data); ...; p.close().
  43. # The dtd is defined by deriving a class which defines methods
  44. # with special names to handle tags: start_foo and end_foo to handle
  45. # <foo> and </foo>, respectively, or do_foo to handle <foo> by itself.
  46. # (Tags are converted to lower case for this purpose.)  The data
  47. # between tags is passed to the parser by calling self.handle_data()
  48. # with some data as argument (the data may be split up in arbutrary
  49. # chunks).  Entity references are passed by calling
  50. # self.handle_entityref() with the entity reference as argument.
  51.  
  52. class SGMLParser:
  53.  
  54.     # Interface -- initialize and reset this instance
  55.     def __init__(self, verbose=0):
  56.         self.verbose = verbose
  57.         self.reset()
  58.  
  59.     # Interface -- reset this instance.  Loses all unprocessed data
  60.     def reset(self):
  61.         self.rawdata = ''
  62.         self.stack = []
  63.         self.lasttag = '???'
  64.         self.nomoretags = 0
  65.         self.literal = 0
  66.  
  67.     # For derived classes only -- enter literal mode (CDATA) till EOF
  68.     def setnomoretags(self):
  69.         self.nomoretags = self.literal = 1
  70.  
  71.     # For derived classes only -- enter literal mode (CDATA)
  72.     def setliteral(self, *args):
  73.         self.literal = 1
  74.  
  75.     # Interface -- feed some data to the parser.  Call this as
  76.     # often as you want, with as little or as much text as you
  77.     # want (may include '\n').  (This just saves the text, all the
  78.     # processing is done by goahead().)
  79.     def feed(self, data):
  80.         self.rawdata = self.rawdata + data
  81.         self.goahead(0)
  82.  
  83.     # Interface -- handle the remaining data
  84.     def close(self):
  85.         self.goahead(1)
  86.  
  87.     # Internal -- handle data as far as reasonable.  May leave state
  88.     # and data to be processed by a subsequent call.  If 'end' is
  89.     # true, force handling all data as if followed by EOF marker.
  90.     def goahead(self, end):
  91.         rawdata = self.rawdata
  92.         i = 0
  93.         n = len(rawdata)
  94.         while i < n:
  95.             if self.nomoretags:
  96.                 self.handle_data(rawdata[i:n])
  97.                 i = n
  98.                 break
  99.             match = interesting.search(rawdata, i)
  100.             if match: j = match.start(0)
  101.             else: j = n
  102.             if i < j: self.handle_data(rawdata[i:j])
  103.             i = j
  104.             if i == n: break
  105.             if rawdata[i] == '<':
  106.                 if starttagopen.match(rawdata, i):
  107.                     if self.literal:
  108.                         self.handle_data(rawdata[i])
  109.                         i = i+1
  110.                         continue
  111.                     k = self.parse_starttag(i)
  112.                     if k < 0: break
  113.                     i = k
  114.                     continue
  115.                 if endtagopen.match(rawdata, i):
  116.                     k = self.parse_endtag(i)
  117.                     if k < 0: break
  118.                     i =  k
  119.                     self.literal = 0
  120.                     continue
  121.                 if commentopen.match(rawdata, i):
  122.                     if self.literal:
  123.                         self.handle_data(rawdata[i])
  124.                         i = i+1
  125.                         continue
  126.                     k = self.parse_comment(i)
  127.                     if k < 0: break
  128.                     i = i+k
  129.                     continue
  130.                 match = special.match(rawdata, i)
  131.                 if match:
  132.                     if self.literal:
  133.                         self.handle_data(rawdata[i])
  134.                         i = i+1
  135.                         continue
  136.                     i = match.end(0)
  137.                     continue
  138.             elif rawdata[i] == '&':
  139.                 match = charref.match(rawdata, i)
  140.                 if match:
  141.                     name = match.group(1)
  142.                     self.handle_charref(name)
  143.                     i = match.end(0)
  144.                     if rawdata[i-1] != ';': i = i-1
  145.                     continue
  146.                 match = entityref.match(rawdata, i)
  147.                 if match:
  148.                     name = match.group(1)
  149.                     self.handle_entityref(name)
  150.                     i = match.end(0)
  151.                     if rawdata[i-1] != ';': i = i-1
  152.                     continue
  153.             else:
  154.                 raise RuntimeError, 'neither < nor & ??'
  155.             # We get here only if incomplete matches but
  156.             # nothing else
  157.             match = incomplete.match(rawdata, i)
  158.             if not match:
  159.                 self.handle_data(rawdata[i])
  160.                 i = i+1
  161.                 continue
  162.             j = match.end(0)
  163.             if j == n:
  164.                 break # Really incomplete
  165.             self.handle_data(rawdata[i:j])
  166.             i = j
  167.         # end while
  168.         if end and i < n:
  169.             self.handle_data(rawdata[i:n])
  170.             i = n
  171.         self.rawdata = rawdata[i:]
  172.         # XXX if end: check for empty stack
  173.  
  174.     # Internal -- parse comment, return length or -1 if not terminated
  175.     def parse_comment(self, i):
  176.         rawdata = self.rawdata
  177.         if rawdata[i:i+4] <> '<!--':
  178.             raise RuntimeError, 'unexpected call to handle_comment'
  179.         match = commentclose.search(rawdata, i+4)
  180.         if not match:
  181.             return -1
  182.         j = match.start(0)
  183.         self.handle_comment(rawdata[i+4: j])
  184.         j = match.end(0)
  185.         return j-i
  186.  
  187.     # Internal -- handle starttag, return length or -1 if not terminated
  188.     def parse_starttag(self, i):
  189.         rawdata = self.rawdata
  190.         if shorttagopen.match(rawdata, i):
  191.             # SGML shorthand: <tag/data/ == <tag>data</tag>
  192.             # XXX Can data contain &... (entity or char refs)?
  193.             # XXX Can data contain < or > (tag characters)?
  194.             # XXX Can there be whitespace before the first /?
  195.             match = shorttag.match(rawdata, i)
  196.             if not match:
  197.                 return -1
  198.             tag, data = match.group(1, 2)
  199.             tag = string.lower(tag)
  200.             self.finish_shorttag(tag, data)
  201.             k = match.end(0)
  202.             return k
  203.         # XXX The following should skip matching quotes (' or ")
  204.         match = endbracket.search(rawdata, i+1)
  205.         if not match:
  206.             return -1
  207.         j = match.start(0)
  208.         # Now parse the data between i+1 and j into a tag and attrs
  209.         attrs = []
  210.         if rawdata[i:i+2] == '<>':
  211.             # SGML shorthand: <> == <last open tag seen>
  212.             k = j
  213.             tag = self.lasttag
  214.         else:
  215.             match = tagfind.match(rawdata, i+1)
  216.             if not match:
  217.                 raise RuntimeError, 'unexpected call to parse_starttag'
  218.             k = match.end(0)
  219.             tag = string.lower(rawdata[i+1:k])
  220.             self.lasttag = tag
  221.         while k < j:
  222.             match = attrfind.match(rawdata, k)
  223.             if not match: break
  224.             attrname, rest, attrvalue = match.group(1, 2, 3)
  225.             if not rest:
  226.                 attrvalue = attrname
  227.             elif attrvalue[:1] == '\'' == attrvalue[-1:] or \
  228.                  attrvalue[:1] == '"' == attrvalue[-1:]:
  229.                 attrvalue = attrvalue[1:-1]
  230.             attrs.append((string.lower(attrname), attrvalue))
  231.             k = match.end(0)
  232.         if rawdata[j] == '>':
  233.             j = j+1
  234.         self.finish_starttag(tag, attrs)
  235.         return j
  236.  
  237.     # Internal -- parse endtag
  238.     def parse_endtag(self, i):
  239.         rawdata = self.rawdata
  240.         match = endbracket.search(rawdata, i+1)
  241.         if not match:
  242.             return -1
  243.         j = match.start(0)
  244.         tag = string.lower(string.strip(rawdata[i+2:j]))
  245.         if rawdata[j] == '>':
  246.             j = j+1
  247.         self.finish_endtag(tag)
  248.         return j
  249.  
  250.     # Internal -- finish parsing of <tag/data/ (same as <tag>data</tag>)
  251.     def finish_shorttag(self, tag, data):
  252.         self.finish_starttag(tag, [])
  253.         self.handle_data(data)
  254.         self.finish_endtag(tag)
  255.  
  256.     # Internal -- finish processing of start tag
  257.     # Return -1 for unknown tag, 0 for open-only tag, 1 for balanced tag
  258.     def finish_starttag(self, tag, attrs):
  259.         try:
  260.             method = getattr(self, 'start_' + tag)
  261.         except AttributeError:
  262.             try:
  263.                 method = getattr(self, 'do_' + tag)
  264.             except AttributeError:
  265.                 self.unknown_starttag(tag, attrs)
  266.                 return -1
  267.             else:
  268.                 self.handle_starttag(tag, method, attrs)
  269.                 return 0
  270.         else:
  271.             self.stack.append(tag)
  272.             self.handle_starttag(tag, method, attrs)
  273.             return 1
  274.  
  275.     # Internal -- finish processing of end tag
  276.     def finish_endtag(self, tag):
  277.         if not tag:
  278.             found = len(self.stack) - 1
  279.             if found < 0:
  280.                 self.unknown_endtag(tag)
  281.                 return
  282.         else:
  283.             if tag not in self.stack:
  284.                 try:
  285.                     method = getattr(self, 'end_' + tag)
  286.                 except AttributeError:
  287.                     self.unknown_endtag(tag)
  288.                 return
  289.             found = len(self.stack)
  290.             for i in range(found):
  291.                 if self.stack[i] == tag: found = i
  292.         while len(self.stack) > found:
  293.             tag = self.stack[-1]
  294.             try:
  295.                 method = getattr(self, 'end_' + tag)
  296.             except AttributeError:
  297.                 method = None
  298.             if method:
  299.                 self.handle_endtag(tag, method)
  300.             else:
  301.                 self.unknown_endtag(tag)
  302.             del self.stack[-1]
  303.  
  304.     # Overridable -- handle start tag
  305.     def handle_starttag(self, tag, method, attrs):
  306.         method(attrs)
  307.  
  308.     # Overridable -- handle end tag
  309.     def handle_endtag(self, tag, method):
  310.         method()
  311.  
  312.     # Example -- report an unbalanced </...> tag.
  313.     def report_unbalanced(self, tag):
  314.         if self.verbose:
  315.             print '*** Unbalanced </' + tag + '>'
  316.             print '*** Stack:', self.stack
  317.  
  318.     # Example -- handle character reference, no need to override
  319.     def handle_charref(self, name):
  320.         try:
  321.             n = string.atoi(name)
  322.         except string.atoi_error:
  323.             self.unknown_charref(name)
  324.             return
  325.         if not 0 <= n <= 255:
  326.             self.unknown_charref(name)
  327.             return
  328.         self.handle_data(chr(n))
  329.  
  330.     # Definition of entities -- derived classes may override
  331.     entitydefs = \
  332.             {'lt': '<', 'gt': '>', 'amp': '&', 'quot': '"', 'apos': '\''}
  333.  
  334.     # Example -- handle entity reference, no need to override
  335.     def handle_entityref(self, name):
  336.         table = self.entitydefs
  337.         if table.has_key(name):
  338.             self.handle_data(table[name])
  339.         else:
  340.             self.unknown_entityref(name)
  341.             return
  342.  
  343.     # Example -- handle data, should be overridden
  344.     def handle_data(self, data):
  345.         pass
  346.  
  347.     # Example -- handle comment, could be overridden
  348.     def handle_comment(self, data):
  349.         pass
  350.  
  351.     # To be overridden -- handlers for unknown objects
  352.     def unknown_starttag(self, tag, attrs): pass
  353.     def unknown_endtag(self, tag): pass
  354.     def unknown_charref(self, ref): pass
  355.     def unknown_entityref(self, ref): pass
  356.  
  357.  
  358. class TestSGMLParser(SGMLParser):
  359.  
  360.     def __init__(self, verbose=0):
  361.         self.testdata = ""
  362.         SGMLParser.__init__(self, verbose)
  363.  
  364.     def handle_data(self, data):
  365.         self.testdata = self.testdata + data
  366.         if len(`self.testdata`) >= 70:
  367.             self.flush()
  368.  
  369.     def flush(self):
  370.         data = self.testdata
  371.         if data:
  372.             self.testdata = ""
  373.             print 'data:', `data`
  374.  
  375.     def handle_comment(self, data):
  376.         self.flush()
  377.         r = `data`
  378.         if len(r) > 68:
  379.             r = r[:32] + '...' + r[-32:]
  380.         print 'comment:', r
  381.  
  382.     def unknown_starttag(self, tag, attrs):
  383.         self.flush()
  384.         if not attrs:
  385.             print 'start tag: <' + tag + '>'
  386.         else:
  387.             print 'start tag: <' + tag,
  388.             for name, value in attrs:
  389.                 print name + '=' + '"' + value + '"',
  390.             print '>'
  391.  
  392.     def unknown_endtag(self, tag):
  393.         self.flush()
  394.         print 'end tag: </' + tag + '>'
  395.  
  396.     def unknown_entityref(self, ref):
  397.         self.flush()
  398.         print '*** unknown entity ref: &' + ref + ';'
  399.  
  400.     def unknown_charref(self, ref):
  401.         self.flush()
  402.         print '*** unknown char ref: &#' + ref + ';'
  403.  
  404.     def close(self):
  405.         SGMLParser.close(self)
  406.         self.flush()
  407.  
  408.  
  409. def test(args = None):
  410.     import sys
  411.  
  412.     if not args:
  413.         args = sys.argv[1:]
  414.  
  415.     if args and args[0] == '-s':
  416.         args = args[1:]
  417.         klass = SGMLParser
  418.     else:
  419.         klass = TestSGMLParser
  420.  
  421.     if args:
  422.         file = args[0]
  423.     else:
  424.         file = 'test.html'
  425.  
  426.     if file == '-':
  427.         f = sys.stdin
  428.     else:
  429.         try:
  430.             f = open(file, 'r')
  431.         except IOError, msg:
  432.             print file, ":", msg
  433.             sys.exit(1)
  434.  
  435.     data = f.read()
  436.     if f is not sys.stdin:
  437.         f.close()
  438.  
  439.     x = klass()
  440.     for c in data:
  441.         x.feed(c)
  442.     x.close()
  443.  
  444.  
  445. if __name__ == '__main__':
  446.     test()
  447.